/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "commonheaders.h"
#include "ctrl.h"
#include "win2k.h"

#define MAX_CAT			64

//#define BTN_MENU		( WM_USER+1 )
//#define BTN_EDIT		( WM_USER+2 )
#define EDIT_VALUE		( WM_USER+3 )

#define CBEXM_MENIITEMFIRST ( WM_USER+100 )	// caution: after this message there must be room for all menuitem commnds
											// so it should be the last message with the highest value

typedef struct TCbExItem
{
	WORD	wMask;
	WORD	wFlags;
	DWORD	dwID;
	TCHAR	szCat[MAX_CAT];
	LPTSTR	pszVal;
	LPCSTR	pszIcon;
	HICON	hIcon;
} CBEXITEMINTERN, *LPCBEXITEMINTERN;

typedef struct TComboEx 
{
	LPCBEXITEMINTERN	pItems;
	INT			numItems;
	INT			numOther;
	INT			iSelectedItem;

	BOOLEAN		bIsChanged;
	BOOLEAN		bLocked;
	BOOLEAN		bIsEditChanged;

	HINSTANCE	hInstance;
	HFONT		hFont;
	HWND		hEdit;
	HWND		hBtnMenu;
	HWND		hBtnEdit;
	HWND		hBtnAdd;
	HWND		hBtnDel;
	RECT		rect;
} CBEX, *LPCBEX;


static INT compareProc( LPCVOID cbi1, LPCVOID cbi2 )
{
	return _tcscmp( (( LPCBEXITEMINTERN )cbi1 )->szCat, ( ( LPCBEXITEMINTERN )cbi2 )->szCat );
}

static INT CheckPhoneSyntax( LPTSTR pszSrc, LPTSTR szNumber, WORD cchNumber, INT& errorPos )
{
	INT lenNum = 0;
	BOOLEAN	hasLeftBreaket = FALSE,
			hasRightBreaket = FALSE;

	if( !szNumber || !pszSrc || !*pszSrc || !cchNumber ) return 0;
	*szNumber = 0;
	errorPos = -1;
	
	if( *pszSrc != '+' ) {
		errorPos = 2; // set cursor after first digit
		*( szNumber + lenNum++ ) = '+';
	}
	else
		*( szNumber + lenNum++ ) = *( pszSrc++ );

	for( ; lenNum < cchNumber - 1 && *pszSrc != 0; pszSrc++ ) {
		switch( *pszSrc ) {
			case '(':
				if( hasLeftBreaket ) {
					if( errorPos == -1 ) errorPos = lenNum;
					break;
				}
				if( *( szNumber + lenNum - 1 ) != ' ' ) {
					*( szNumber + lenNum++ ) = ' ';
					if( errorPos == -1 ) errorPos = lenNum + 1;
				}
				*( szNumber + lenNum++ ) = *pszSrc;
				hasLeftBreaket = TRUE;
				break;

			case ')':
				if( hasRightBreaket ) {
					if( errorPos == -1 ) errorPos = lenNum;
					break;
				}
				*( szNumber + lenNum++ ) = *pszSrc;
				if( *( pszSrc + 1 ) != ' ' ) {
					*( szNumber + lenNum++ ) = ' ';
					if( errorPos == -1 ) errorPos = lenNum;
				}
				hasRightBreaket = TRUE;
				break;

			case ' ':
				if( *( szNumber + lenNum - 1 ) != ' ' ) {
					*( szNumber + lenNum++ ) = *pszSrc;
				}
				else
					if( errorPos == -1 ) errorPos = lenNum;
				break;

			default:
				if( *pszSrc >= '0' && *pszSrc <= '9' || *pszSrc == '-' ) {
					*( szNumber + lenNum++ ) = *pszSrc;
				}
				// remember first error position
				else if( errorPos == -1 ) errorPos = lenNum;
				break;
		}
	}
	*( szNumber + lenNum ) = 0;
	return lenNum;
}

/**
 * name:   DlgProc_EditEMail()
 * desc:   dialog procedure
 *
 * return:   0 or 1
 **/
static INT CALLBACK DlgProc_EMail( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg ) {
		case WM_INITDIALOG:
		{
			LPCBEXITEM cbi = ( LPCBEXITEM )lParam;
			const NIcoLib::ICONCTRL idIcon[] = {
				{ ICO_DLG_EMAIL,	WM_SETICON,		NULL		},
				{ ICO_DLG_EMAIL,	STM_SETIMAGE,	ICO_DLGLOGO	},
				{ ICO_BTN_OK,		BM_SETIMAGE,	IDOK		},
				{ ICO_BTN_CANCEL,	BM_SETIMAGE,	IDCANCEL	}
			};

			const INT numIconsToSet = DB::Setting::GetByte( SET_ICONS_BUTTONS, 1 ) ? SIZEOF( idIcon ) : 2;

			if( !cbi ) return FALSE;
			SetUserData( hDlg, lParam );
				
			NIcoLib::SetCtrlIcons( hDlg, idIcon, numIconsToSet );

			if( *cbi->pszVal ) SetWindowText( hDlg, LPGENT( "Edit E-Mail") );
			TranslateDialogDefault( hDlg );
			SendDlgItemMessage( hDlg, EDIT_CATEGORY, EM_LIMITTEXT, cbi->ccCat - 1, 0 );
			SendDlgItemMessage( hDlg, EDIT_EMAIL, EM_LIMITTEXT, cbi->ccVal - 1, 0 );
			SetDlgItemText( hDlg, EDIT_CATEGORY, cbi->pszCat );
			SetDlgItemText( hDlg, EDIT_EMAIL, cbi->pszVal );
			EnableWindow( GetDlgItem( hDlg, EDIT_CATEGORY ), !( cbi->wFlags & CBEXIF_CATREADONLY ) );
			EnableWindow( GetDlgItem( hDlg, IDOK ), *cbi->pszVal );
			// translate Userinfo buttons
			{
				TCHAR szButton[MAX_PATH];
				HWND hBtn;
				
				hBtn = GetDlgItem( hDlg, IDOK );
				GetWindowText( hBtn, szButton, MAX_PATH );
				SetWindowText( hBtn, TranslateTS( szButton ) );
				hBtn = GetDlgItem( hDlg, IDCANCEL );
				GetWindowText( hBtn, szButton, MAX_PATH );
				SetWindowText( hBtn, TranslateTS( szButton ) );
			}
			// set bold font for name in description area
			{
				LOGFONT lf;
				HFONT hNormalFont = ( HFONT )SendDlgItemMessage( hDlg, TXT_NAME, WM_GETFONT, 0, 0 );
				GetObject( hNormalFont, sizeof( lf ), &lf );
				lf.lfWeight = FW_BOLD;
				hNormalFont = CreateFontIndirect( &lf );
				SendDlgItemMessage( hDlg, TXT_NAME, WM_SETFONT, ( WPARAM )hNormalFont, 0 );
			}
			return TRUE;
		}

		case WM_CTLCOLORSTATIC:
	            
			switch( GetDlgCtrlID( ( HWND )lParam ) ) {
				case STATIC_WHITERECT:
				case ICO_DLGLOGO:
				case TXT_NAME:
				case TXT_DESCRIPTION:
				SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
				return ( BOOL )GetStockObject( WHITE_BRUSH );
			}
			break;
	      
		case WM_COMMAND:
			switch( LOWORD( wParam ) ) {
				if( HIWORD( wParam ) == BN_CLICKED ) {
					case IDOK:
					{
						LPCBEXITEM cbi = ( LPCBEXITEM )GetUserData( hDlg );

						if( cbi->pszVal && cbi->ccVal > 0 )
							GetDlgItemText( hDlg, EDIT_EMAIL, cbi->pszVal, cbi->ccVal );
						if( cbi->pszCat && cbi->ccCat > 0 )
							GetDlgItemText( hDlg, EDIT_CATEGORY, cbi->pszCat, cbi->ccCat );
					}
					case IDCANCEL:
						EndDialog( hDlg, LOWORD( wParam ) );
						break;
				}
				case EDIT_EMAIL:
					if( HIWORD( wParam ) == EN_UPDATE ) {
						TCHAR szText[MAXDATASIZE];
						LPTSTR pszAdd, pszDot;
						LPCBEXITEM cbi = ( LPCBEXITEM )GetUserData( hDlg );

						if( PtrIsValid( cbi ) ) {
							GetWindowText( ( HWND )lParam, szText, (sizeof( szText ))/(sizeof( szText[0]))  );
							EnableWindow( GetDlgItem( hDlg, IDOK ), 
								( ( pszAdd = _tcschr( szText, '@') ) && 
								*( pszAdd + 1 ) != '.' &&
								( pszDot = _tcschr( pszAdd, '.') ) &&
								*( pszDot + 1 ) &&
								_tcscmp( szText, cbi->pszVal ) ) );
						}
					}
					break;
			}
			break;
		case WM_DESTROY:
			DeleteObject( ( HFONT )SendDlgItemMessage( hDlg, TXT_NAME, WM_GETFONT, 0, 0 ) );
			break;
	}
	return FALSE;
}

/**
 * name:   DlgProc_EditPhone()
 * desc:   dialog procedure
 *
 * return:   0 or 1
 **/
INT CALLBACK DlgProc_Phone( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg ) {
		case WM_INITDIALOG:
		{
			LPCBEXITEM cbi = ( LPCBEXITEM )lParam;
			INT i, item, countryCount;
			LPIDSTRLIST	pCountries;
			HWND hCombo = GetDlgItem( hDlg, EDIT_COUNTRY );
			const NIcoLib::ICONCTRL idIcon[] = {
				{ ICO_DLG_PHONE,	WM_SETICON,		NULL		},
				{ ICO_DLG_PHONE,	STM_SETIMAGE,	ICO_DLGLOGO	},
				{ ICO_BTN_OK,		BM_SETIMAGE,	IDOK		},
				{ ICO_BTN_CANCEL,	BM_SETIMAGE,	IDCANCEL	}
			};
			const INT numIconsToSet = DB::Setting::GetByte( SET_ICONS_BUTTONS, 1 ) ? SIZEOF( idIcon ) : 2;

			if( !cbi ) return FALSE;
			SetUserData( hDlg, lParam );

			NIcoLib::SetCtrlIcons( hDlg, idIcon, numIconsToSet );

			// translate Userinfo buttons
			{
				TCHAR szButton[MAX_PATH];
				HWND hBtn;
				
				hBtn = GetDlgItem( hDlg, IDOK );
				GetWindowText( hBtn, szButton, MAX_PATH );
				SetWindowText( hBtn, TranslateTS( szButton ) );
				hBtn = GetDlgItem( hDlg, IDCANCEL );
				GetWindowText( hBtn, szButton, MAX_PATH );
				SetWindowText( hBtn, TranslateTS( szButton ) );
			}
			if( *cbi->pszVal ) SetWindowText( hDlg, LPGENT( "Edit Phone Number") );
			if( cbi->wFlags & CBEXIF_SMS ) CheckDlgButton( hDlg, CHECK_SMS, BST_CHECKED );
			TranslateDialogDefault( hDlg );
	            
			EnableWindow( GetDlgItem( hDlg, IDOK ), *cbi->pszVal );
			SendDlgItemMessage( hDlg, EDIT_AREA, EM_LIMITTEXT, 31, 0 );
			SendDlgItemMessage( hDlg, EDIT_NUMBER, EM_LIMITTEXT, 63, 0 );
			SendDlgItemMessage( hDlg, EDIT_CATEGORY, EM_LIMITTEXT, cbi->ccCat - 1, 0 );
			SendDlgItemMessage( hDlg, EDIT_PHONE, EM_LIMITTEXT, cbi->ccVal - 1, 0 );
			
			GetCountryList( ( WPARAM )&countryCount, ( LPARAM )&pCountries );
			for( i = 0; i < countryCount; i++ ) {
				if( pCountries[i].nID == 0 || pCountries[i].nID == 0xFFFF ) continue;
				item = SendMessage( hCombo, CB_ADDSTRING, NULL, ( LPARAM )pCountries[i].ptszTranslated );
				SendMessage( hCombo, CB_SETITEMDATA, item, pCountries[i].nID );
			}

			SetDlgItemText( hDlg, EDIT_PHONE, cbi->pszVal );
			SetDlgItemText( hDlg, EDIT_CATEGORY, cbi->pszCat );
			EnableWindow( GetDlgItem( hDlg, EDIT_CATEGORY ), !( cbi->wFlags & CBEXIF_CATREADONLY ) );
			return TRUE;
		}
		case WM_CTLCOLORSTATIC:
			switch( GetDlgCtrlID( ( HWND )lParam ) ) {
				case STATIC_WHITERECT:
				case ICO_DLGLOGO:
				case TXT_NAME:
					SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
					return ( BOOL )GetStockObject( WHITE_BRUSH );
			}
			break;

		case WM_COMMAND:
		{
			static INT noRecursion=0;
	          
			switch( LOWORD( wParam ) ) {
				if( HIWORD( wParam ) == BN_CLICKED ) {
					case IDOK:
					{   
						LPCBEXITEM cbi = ( LPCBEXITEM )GetUserData( hDlg );
						TCHAR szText[MAXDATASIZE];
						INT errorPos;

						if( !GetDlgItemText( hDlg, EDIT_PHONE, szText, MAXDATASIZE ) || !CheckPhoneSyntax( szText, cbi->pszVal, cbi->ccVal, errorPos ) || errorPos > -1 ) {
							MsgErr( hDlg, TranslateT( "The phone number should start with a + and consist of\nnumbers, spaces, brackets and hyphens only.") );
							/*
							EDITBALLOONTIP btt;

							btt.cbStruct = sizeof( btt );
#ifdef _UNICODE
							btt.pszTitle = TranslateW( L"Syntax Error" );
							btt.pszText = TranslateW( L"The phone number should start with a + and consist of\nnumbers, spaces, brackets and hyphens only." );
#else
							WCHAR wszTitle[MAX_PATH];
							WCHAR wszText[MAX_PATH];
							LPSTR pszTitle = Translate( "Syntax Error" );
							LPSTR pszText = Translate( "The phone number should start with a + and consist of\nnumbers, spaces, brackets and hyphens only." );
							INT cbTitle = mir_strlen( pszTitle ) + 1;
							INT cbText = mir_strlen( pszText ) + 1;

							MultiByteToWideChar( myGlobals.CodePage, 0, pszTitle, -1, wszTitle, cbTitle );
							MultiByteToWideChar( myGlobals.CodePage, 0, pszText, -1, wszText, cbText );
							btt.pszTitle = wszTitle;
							btt.pszText = wszText;
#endif
							SendDlgItemMessage( hDlg, EDIT_PHONE, EM_SHOWBALLOONTIP, NULL, ( LPARAM )&btt );
							SetDlgItemText( hDlg, EDIT_PHONE, cbi->pszVal );
							SendDlgItemMessage( hDlg, EDIT_PHONE, EM_SETSEL, errorPos, errorPos );
							*/
							break;
						}
						// save category string
						GetDlgItemText( hDlg, EDIT_CATEGORY, cbi->pszCat, cbi->ccCat );

						// save SMS flag
						if( IsDlgButtonChecked( hDlg, CHECK_SMS ) != ( ( cbi->wFlags & CBEXIF_SMS ) == CBEXIF_SMS ) ) {
							cbi->wFlags ^= CBEXIF_SMS;
						}
					}
					//fall through
					case IDCANCEL:
						EndDialog( hDlg, wParam );
						break;
				}
	              
				case EDIT_COUNTRY:
					if( HIWORD( wParam ) != CBN_SELCHANGE ) break;

				case EDIT_AREA:
				case EDIT_NUMBER:
					if( LOWORD( wParam ) != EDIT_COUNTRY && HIWORD( wParam ) != EN_CHANGE ) break;
					if( noRecursion ) break;
					EnableWindow( GetDlgItem( hDlg, IDOK ), TRUE );
					{   
						TCHAR szPhone[MAXDATASIZE], szArea[32], szData[64];
						INT   nCurSel = SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_GETCURSEL, 0, 0 );   
						UINT  nCountry = ( nCurSel != CB_ERR ) ? SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_GETITEMDATA, nCurSel, 0 ) : 0;

						GetDlgItemText( hDlg, EDIT_AREA, szArea, (sizeof( szArea ))/(sizeof( szArea[0])) );
						GetDlgItemText( hDlg, EDIT_NUMBER, szData, (sizeof( szData ))/(sizeof( szData[0])) );
						mir_snprintfT( szPhone, MAXDATASIZE, _T( "+%u (%s) %s" ), nCountry, szArea, szData );
						noRecursion = 1;
						SetDlgItemText( hDlg, EDIT_PHONE, szPhone );
						noRecursion = 0;
					}
					break;

				case EDIT_PHONE:
					if( HIWORD( wParam ) != EN_UPDATE ) break;
					if( noRecursion ) break;
					noRecursion = 1;
					{   
						TCHAR szText[MAXDATASIZE], *pText, *pArea, *pNumber;
						INT isValid = 1;
						GetDlgItemText( hDlg, EDIT_PHONE, szText, (sizeof( szText ))/(sizeof( szText[0]))  );
						if( szText[0] != '+' ) isValid = 0;
						if( isValid ) {
							INT i,country = _tcstol( szText + 1, &pText, 10 );
							if( pText - szText > 4 )
								isValid = 0;
							else {
								for( i = SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_GETCOUNT, 0, 0 ) - 1; i >= 0; i-- ) {
									if( country == SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_GETITEMDATA, i, 0 ) ) {
										SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_SETCURSEL, i, 0 ); 
										break;
									}
								}
							}
							if( i < 0 ) isValid=0;
						}
						if( isValid ) {
							pArea = pText + _tcscspn( pText, _T( "0123456789" ) );
							pText = pArea + _tcsspn( pArea, _T( "0123456789" ) );
							if( *pText ) {
								*pText = '\0';
								pNumber = pText + 1 + _tcscspn( pText + 1, _T( "0123456789" ) );
								SetDlgItemText( hDlg, EDIT_NUMBER, pNumber );
							}
							SetDlgItemText( hDlg, EDIT_AREA, pArea );
						}
						if( !isValid ) {
							SendDlgItemMessage( hDlg, EDIT_COUNTRY, CB_SETCURSEL, -1, 0 );
							SetDlgItemText( hDlg, EDIT_AREA, _T( "" ) );
							SetDlgItemText( hDlg, EDIT_NUMBER, _T( "" ) );
						}
					}
					noRecursion = 0;
					EnableWindow( GetDlgItem( hDlg, IDOK ), GetWindowTextLength( GetDlgItem( hDlg, EDIT_PHONE ) ) );
					break;
			}
			break;
		}
	}
	return FALSE;
}

/**
 * name:	CbEx_WndProc
 * desc:	window procedure for the extended combobox class
 * param:	hwnd	- handle to a extended combobox window
 *			msg		- message to handle
 *			wParam	- message specific
 *			lParam	- message specific
 * return:	message specific
 **/
static LRESULT CALLBACK CbEx_WndProc( HWND hwnd, UINT msg,  WPARAM wParam, LPARAM lParam ) 
{
	LPCBEX	cbex = ( LPCBEX )GetWindowLongPtr( hwnd, 0 );

	switch( msg ) {

		/**
		 * name:	WM_NCCREATE
		 * desc:	is called to initiate the window creation
		 * param:	wParam - not used
		 *			lParam - pointer to a CREATESTRUCT
		 *
		 * return:	FALSE on error, TRUE if initialisation was ok
		 **/
		case WM_NCCREATE:
		{
			LPCREATESTRUCT cs = ( LPCREATESTRUCT )lParam;

			if( !( cbex = ( LPCBEX )calloc( 1,sizeof( CBEX ) ) ) )
				return FALSE;
			SetWindowLongPtr( hwnd, 0, ( LONG_PTR )cbex );
			cbex->bLocked = 1;
			cbex->hFont = ( HFONT )GetStockObject( DEFAULT_GUI_FONT );
			cbex->hInstance = cs->hInstance;
			cbex->iSelectedItem = -1;
			cbex->rect.left = cs->x;
			cbex->rect.top = cs->y;
			cbex->rect.right = cs->x + cs->cx;
			cbex->rect.bottom = cs->y + cs->cy;
			return TRUE;
		}

		/**
		 * name:	WM_NCCREATE
		 * desc:	is called to create all subitems
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	FALSE on error, TRUE if initialisation was ok
		 **/
		case WM_CREATE:
		{
			WORD wHeight = ( WORD )( cbex->rect.bottom - cbex->rect.top );
			WORD wWidth = 130;
			WORD x = 0;

			if( !( cbex->hBtnEdit = CreateWindowEx( WS_EX_NOPARENTNOTIFY,
					UINFOBUTTONCLASS, 
					_T( "none" ),
					WS_VISIBLE|WS_CHILD|WS_TABSTOP, 0, 0,
					wWidth, wHeight,
					hwnd,
					NULL,
					cbex->hInstance, NULL ) ) ) {
				cbex->bLocked = 0;
				return FALSE;
			}
			x += wWidth + 2;
			wWidth = wHeight;
			if( !( cbex->hBtnMenu = CreateWindowEx( WS_EX_NOPARENTNOTIFY,
					UINFOBUTTONCLASS,
					NULL,
					WS_VISIBLE|WS_CHILD|WS_TABSTOP|MBS_PUSHBUTTON|MBS_DOWNARROW,
					x, 0,
					wWidth, wHeight,
					hwnd,
					NULL,
					cbex->hInstance, NULL ) ) ) {
				DestroyWindow( cbex->hBtnEdit );
				cbex->bLocked = 0;
				return FALSE;
			}
			x += wWidth + 2;
			wWidth = ( WORD )( cbex->rect.right - cbex->rect.left - x - ( 2 * ( wHeight + 2 ) ) );
			if( !( cbex->hEdit = CreateWindowEx( WS_EX_CLIENTEDGE,
					_T( "Edit" ), 
					NULL,
					WS_VISIBLE|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL,
					x, 1,
					wWidth,	wHeight - 2,
					hwnd,
					NULL,
					cbex->hInstance, NULL ) ) ) {
				DestroyWindow( cbex->hBtnEdit );
				DestroyWindow( cbex->hBtnMenu );
				cbex->bLocked = 0;
				return FALSE;
			}
			x += wWidth + 2;
			wWidth = wHeight;
			if( !( cbex->hBtnAdd = CreateWindowEx( WS_EX_NOPARENTNOTIFY,
					UINFOBUTTONCLASS,
					NULL,
					WS_VISIBLE|WS_CHILD|WS_TABSTOP|MBS_FLAT,
					x, 0,
					wWidth, wHeight,
					hwnd,
					NULL,
					cbex->hInstance, NULL ) ) ) {
				DestroyWindow( cbex->hBtnEdit );
				DestroyWindow( cbex->hBtnMenu );
				DestroyWindow( cbex->hEdit );
				cbex->bLocked = 0;
				return FALSE;
			}
			x += wWidth + 2;
			if( !( cbex->hBtnDel = CreateWindowEx( WS_EX_NOPARENTNOTIFY,
					UINFOBUTTONCLASS,
					NULL,
					WS_VISIBLE|WS_CHILD|WS_TABSTOP|MBS_FLAT,
					x, 0,
					wWidth, wHeight,
					hwnd,
					NULL,
					cbex->hInstance, NULL ) ) ) {
				DestroyWindow( cbex->hBtnEdit );
				DestroyWindow( cbex->hBtnMenu );
				DestroyWindow( cbex->hEdit );
				DestroyWindow( cbex->hBtnAdd );
				cbex->bLocked = 0;
				return FALSE;
			}

			// set ids
			SetWindowLongPtr( cbex->hBtnEdit, GWL_ID, BTN_EDIT );
			SetWindowLongPtr( cbex->hBtnMenu, GWL_ID, BTN_MENU );
			SetWindowLongPtr( cbex->hEdit, GWL_ID, EDIT_VALUE );
			SetWindowLongPtr( cbex->hBtnAdd, GWL_ID, BTN_ADD );
			SetWindowLongPtr( cbex->hBtnDel, GWL_ID, BTN_DEL );
			// set fonts & maximum edit control charachters
			SendMessage( cbex->hEdit, WM_SETFONT, ( WPARAM )cbex->hFont, NULL );
			SendMessage( cbex->hEdit, EM_LIMITTEXT, ( WPARAM )MAXDATASIZE, NULL );
			// add tooltips
			SendMessage( cbex->hBtnMenu, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Choose the item to display." ), MBF_TCHAR );
			SendMessage( cbex->hBtnEdit, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Edit the currently displayed item." ), MBF_TCHAR );
			SendMessage( cbex->hBtnAdd, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Add a new custom item." ), MBF_TCHAR );
			SendMessage( cbex->hBtnDel, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Delete the selected item." ), MBF_TCHAR );
			// reload icons
			CbEx_WndProc( hwnd, WM_SETICON, NULL, NULL );
			cbex->bLocked = 0;
			return TRUE;
		}

		/**
		 * name:	WM_DESTROY
		 * desc:	default destroy message, so clear up memory
		 * param:	wParam - not used
		 *			lParam - not used
		 * return:	return value of DefWindowProc
		 **/
		case WM_DESTROY:
			CbEx_WndProc( hwnd, CBEXM_DELALLITEMS, NULL, NULL );
			DestroyWindow( cbex->hBtnEdit );
			DestroyWindow( cbex->hBtnMenu );
			DestroyWindow( cbex->hBtnAdd );
			DestroyWindow( cbex->hBtnDel );
			DestroyWindow( cbex->hEdit );
			FREE( cbex );
			break;

		/**
		 * name:	WM_CTLCOLOREDIT
		 * desc:	is called on a paint message for a dialog item to determine its colour scheme
		 * param:	wParam - pointer to a HDC
		 *			lParam - pointer to a HWND
		 * return:	a brush
		 **/
		case WM_CTLCOLOREDIT:
			if( !DB::Setting::GetByte( SET_PROPSHEET_SHOWCOLOURS, 1 ) || ( HWND )lParam != cbex->hEdit || !cbex->pItems || cbex->iSelectedItem < 0 ) 
				break;
			return Ctrl_SetTextColour( ( HDC )wParam, cbex->pItems[cbex->iSelectedItem].wFlags );

		case WM_CTLCOLORSTATIC:
			if( ( HWND )lParam == cbex->hEdit )
					return ( BOOL )GetSysColor( COLOR_WINDOW );
			return FALSE;
		/**
		 * name:	WM_SETICON
		 * desc:	updates the icons of this control
		 * param:	wParam - not used
		 *			lParam - not used
		 * return:	always 0
		 **/
		case WM_SETICON:
		{
			HICON hIcon;
			INT i;

			hIcon = NIcoLib::GetIcon( ICO_BTN_ADD );
			SendMessage( cbex->hBtnAdd, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIcon );
			SendMessage( cbex->hBtnAdd, WM_SETTEXT, NULL, ( LPARAM )( hIcon ? _T( "" ) : _T( "+" ) ) );

			hIcon = NIcoLib::GetIcon( ICO_BTN_DELETE );
			SendMessage( cbex->hBtnDel, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIcon );
			SendMessage( cbex->hBtnDel, WM_SETTEXT, NULL, ( LPARAM )( hIcon ? _T( "" ) : _T( "-" ) ) );

			if( cbex->pItems && cbex->numItems > 0 ) {
				for( i = 0; i < cbex->numItems; i++ ) {
					cbex->pItems[i].hIcon = NIcoLib::GetIcon( cbex->pItems[i].pszIcon );
				}
				if( cbex->iSelectedItem >= 0 && cbex->iSelectedItem < cbex->numItems )
					SendMessage( cbex->hBtnEdit, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )cbex->pItems[cbex->iSelectedItem].hIcon );
			}
			return 0;
		}

		case WM_COMMAND:
			switch( LOWORD( wParam ) ) {
				/**
				 * name:	BTN_MENU
				 * desc:	the button to dropdown the list to show all items is pressed
				 **/
				case BTN_MENU:
					if( HIWORD( wParam ) == BN_CLICKED ) {
						POINT pt = { 0, 0 };
						RECT rc;
						MENUITEMINFO mii;
						INT i, nItems;
						HMENU hMenu;

						if( !( hMenu = CreatePopupMenu() ) ) return 0;
						SetFocus( ( HWND )lParam );

						ZeroMemory( &mii, sizeof( MENUITEMINFO ) );
						mii.cbSize = sizeof( MENUITEMINFO );
						mii.fMask = MIIM_ID|MIIM_STRING|MIIM_FTYPE|MIIM_STATE;
						mii.fType = MFT_STRING;

						// insert the items
						for( i = nItems = 0; i < cbex->numItems; i++ ) {
							if( ( cbex->pItems[i].wFlags & CBEXIF_DELETED ) || *cbex->pItems[i].szCat == 0 ) continue;
							mii.fState = ( cbex->pItems[i].pszVal && *cbex->pItems[i].pszVal ) ? MFS_CHECKED : MFS_UNCHECKED;
							mii.wID = CBEXM_MENIITEMFIRST + i;
							mii.dwTypeData = cbex->pItems[i].szCat;
							if( !InsertMenuItem( hMenu, i, TRUE, &mii ) ) {
								DestroyMenu( hMenu );
								return 0;
							}
							nItems++;
						}
						// add separator between default and custom items
						if( nItems > 3 ) {
							mii.fMask = MIIM_FTYPE;
							mii.fType = MFT_SEPARATOR;
							mii.wID = 0;
							mii.dwItemData = 0;
							InsertMenuItem( hMenu, 3, TRUE, &mii );
						}
						ClientToScreen( ( HWND )lParam, &pt );
						GetClientRect( ( HWND )lParam, &rc );
						i = TrackPopupMenuEx( hMenu, TPM_RIGHTALIGN|TPM_RETURNCMD, pt.x + rc.right, pt.y + rc.bottom, hwnd, NULL );
						SendMessage( cbex->hBtnMenu, BM_SETCHECK, NULL, NULL );
						if( i >= CBEXM_MENIITEMFIRST && i < CBEXM_MENIITEMFIRST + cbex->numItems ) {
							CbEx_WndProc( hwnd, CBEXM_SETCURSEL, ( WPARAM )i - CBEXM_MENIITEMFIRST, NULL );
						}
						DestroyMenu( hMenu );
						return 0;
					}
					break;

				/**
				 * name:	BTN_ADD
				 * desc:	the button to add a new entry is pressed
				 **/
				case BTN_ADD:
					if( HIWORD( wParam ) == BN_CLICKED ) {
						DLGPROC dlgProc;
						WORD dlgID;
						TCHAR szCat[MAX_CAT] = { 0 };
						TCHAR szVal[MAXDATASIZE] = { 0 };
						CBEXITEM cbi;
						HWND hDlgDetails;

						SetFocus( ( HWND )lParam );
						if( !( hDlgDetails = GetParent( GetParent( hwnd ) ) ) ) return 1;
						if( SendMessage( hDlgDetails, PSM_ISLOCKED, NULL, NULL ) ) return 0;
						
						switch( GetWindowLong( hwnd, GWL_ID ) ) {
							case EDIT_PHONE:
								dlgID = IDD_ADDPHONE;
								dlgProc = ( DLGPROC )DlgProc_Phone;
								cbi.pszIcon = ICO_BTN_CUSTOMPHONE;
								break;
							case EDIT_EMAIL:
								dlgID = IDD_ADDEMAIL;
								dlgProc = ( DLGPROC )DlgProc_EMail;
								cbi.pszIcon = ICO_BTN_EMAIL;
								break;
							default:
								return 1;
						}
							
						cbi.iItem = -1;
						cbi.wMask = CBEXIM_CAT|CBEXIM_VAL|CBEXIM_FLAGS|CBEXIM_ICONTEXT;
						cbi.pszCat = szCat;
						cbi.pszVal = szVal;
						cbi.ccCat = MAX_CAT;
						cbi.ccVal = MAXDATASIZE;
						cbi.wFlags = 0;
						cbi.dwID = 0;

						if( DialogBoxParam( ghInst, MAKEINTRESOURCE( dlgID ), GetParent( hwnd ), dlgProc, ( LPARAM )&cbi ) == IDOK ) {
							HANDLE hContact = NULL;
							
							SendMessage( hDlgDetails, PSM_GETCONTACT, NULL, ( LPARAM )&hContact );
							if( hContact ) cbi.wFlags |= CTRLF_HASCUSTOM;
							cbi.wFlags |= CTRLF_CHANGED;
							if( SendMessage( hwnd, CBEXM_ADDITEM, NULL, ( LPARAM )&cbi ) > CB_ERR ) {
								SendMessage( hDlgDetails, PSM_CHANGED, NULL, NULL );
								cbex->bIsChanged = TRUE;
								SendMessage( hwnd, CBEXM_SETCURSEL, cbex->numItems - 1, NULL );
							}
						}
						return 0;
					}
					break;

				/**
				 * name:	BTN_EDIT
				 * desc:	the button to edit an existing entry is pressed
				 **/
				case BTN_EDIT:
					if( HIWORD( wParam ) == BN_CLICKED ) {
						DLGPROC dlgProc;
						WORD dlgID;
						TCHAR szCat[MAX_CAT] = { 0 };
						TCHAR szVal[MAXDATASIZE] = { 0 };
						CBEXITEM cbi;
						HWND hDlgDetails;

						SetFocus( ( HWND )lParam );
						if( !( hDlgDetails = GetParent( GetParent( hwnd ) ) ) ) return 1;
						if( SendMessage( hDlgDetails, PSM_ISLOCKED, NULL, NULL ) ) return 0;
						if( !cbex->pItems || cbex->iSelectedItem == -1 ) return 0;

						switch( GetWindowLong( hwnd, GWL_ID ) ) {
							case EDIT_PHONE:
								dlgID = IDD_ADDPHONE;
								dlgProc = ( DLGPROC )DlgProc_Phone;
								break;
							case EDIT_EMAIL:
								dlgID = IDD_ADDEMAIL;
								dlgProc = ( DLGPROC )DlgProc_EMail;
								break;
							default:
								return 1;
						}
						cbi.iItem = cbex->iSelectedItem;
						cbi.dwID = 0;
						cbi.wMask = CBEXIM_CAT|CBEXIM_VAL|CBEXIM_FLAGS;
						cbi.pszCat = szCat;
						cbi.pszVal = szVal;
						cbi.ccCat = MAX_CAT;
						cbi.ccVal = MAXDATASIZE;
						if( !CbEx_WndProc( hwnd, CBEXM_GETITEM, NULL, ( LPARAM )&cbi ) ) {
							MsgErr( hwnd, LPGENT( "CRITICAL: Unable to edit current entry!\nThis should not happen!") );
							return 1;
						}

						if( DialogBoxParam( ghInst, MAKEINTRESOURCE( dlgID ), GetParent( hwnd ), dlgProc, ( LPARAM )&cbi ) == IDOK ) {
							HANDLE hContact;

							SendMessage( hDlgDetails, PSM_GETCONTACT, NULL, ( LPARAM )&hContact );
							if( hContact ) cbi.wFlags |= CTRLF_HASCUSTOM;
							cbi.wFlags |= CTRLF_CHANGED;
							SendMessage( hwnd, CBEXM_SETITEM, NULL, ( LPARAM )&cbi );
							SendMessage( hDlgDetails, PSM_CHANGED, NULL, NULL );
							cbex->bIsChanged = TRUE;
						}
						return 0;
					}
					break;

				/**
				 * name:	BTN_DEL
				 * desc:	the button to delete an existing entry is pressed
				 **/
				case BTN_DEL:
					if( HIWORD( wParam ) == BN_CLICKED ) {
						HWND hDlgDetails;
						MSGBOX mBox;
						TCHAR szMsg[MAXDATASIZE];
					
						SetFocus( ( HWND )lParam );
						if( !( hDlgDetails = GetParent( GetParent( hwnd ) ) ) ||
						   SendMessage( hDlgDetails, PSM_ISLOCKED, NULL, NULL ) ||
						   !cbex->pItems ||
						   cbex->iSelectedItem < 0 ||
						   cbex->iSelectedItem >= cbex->numItems ||
						   FAILED( mir_snprintfT( szMsg, MAXDATASIZE, TranslateT( "Do you really want to delete the current selected item?\n\t%s\n\t%s" ),
								cbex->pItems[cbex->iSelectedItem].szCat, cbex->pItems[cbex->iSelectedItem].pszVal ) )
						  )
						{
						   return 1;
						}
						mBox.cbSize = sizeof( MSGBOX );
						mBox.hParent = hDlgDetails;
						mBox.hiLogo = NIcoLib::GetIcon( ICO_DLG_PHONE );
						mBox.uType = MB_YESNO|MB_ICON_QUESTION|MB_NOPOPUP;
						mBox.ptszTitle = TranslateT( "Delete" );
						mBox.ptszMsg = szMsg;
						if( IDYES == MsgBoxService( NULL, ( LPARAM )&mBox ) ) {
							// clear value for standard entry
							if( cbex->pItems[cbex->iSelectedItem].wFlags & CBEXIF_CATREADONLY ) {
								FREE( cbex->pItems[cbex->iSelectedItem].pszVal );
								SetWindowText( cbex->hEdit, _T( "" ) );
								cbex->pItems[cbex->iSelectedItem].wFlags &= ~CBEXIF_SMS;
								cbex->pItems[cbex->iSelectedItem].wFlags |= CTRLF_CHANGED;
							}
							// clear values for customized database entry
							else 
							if( cbex->pItems[cbex->iSelectedItem].dwID != 0 ) {
								FREE( cbex->pItems[cbex->iSelectedItem].pszVal );
								*cbex->pItems[cbex->iSelectedItem].szCat = 0;
								cbex->pItems[cbex->iSelectedItem].wFlags |= CTRLF_CHANGED|CBEXIF_DELETED;
								CbEx_WndProc( hwnd, CBEXM_SETCURSEL, cbex->iSelectedItem - 1, FALSE );
							}
							// delete default entry
							else
								CbEx_WndProc( hwnd, CBEXM_DELITEM, NULL, cbex->iSelectedItem );

							SendMessage( hDlgDetails, PSM_CHANGED, NULL, NULL );
							cbex->bIsChanged = TRUE;
						}
						return 0;
					}
					break;

				/**
				 * name:	EDIT_VALUE
				 * desc:	the edit control wants us to act
				 **/
				case EDIT_VALUE:
					switch( HIWORD( wParam ) ) {
						case EN_UPDATE:
						{
							TCHAR szVal[MAXDATASIZE] = { 0 };
							INT ccVal;
							HANDLE hContact;
							HWND hDlgDetails = GetParent( GetParent( hwnd ) );
							
							EnableWindow( cbex->hBtnDel, GetWindowTextLength( cbex->hEdit ) > 0 );

							if( SendMessage( hDlgDetails, PSM_ISLOCKED, NULL, NULL ) ||
								cbex->bLocked || 
								!cbex->pItems || 
								cbex->iSelectedItem < 0 || 
								cbex->iSelectedItem >= cbex->numItems ) return 1;

							// get the edit control's text value and check it for syntax
							switch( GetWindowLong( hwnd, GWL_ID ) ) {
								case EDIT_PHONE:
								{
									INT errorPos;
									TCHAR szEdit[MAXDATASIZE];

									if( ccVal = GetWindowText( cbex->hEdit, szEdit, MAXDATASIZE ) ) {
										if( !( ccVal = CheckPhoneSyntax( szEdit, szVal, MAXDATASIZE, errorPos ) ) || errorPos > -1 ) {
											SetWindowText( cbex->hEdit, szVal );
											SendMessage( cbex->hEdit, EM_SETSEL, errorPos, errorPos );
										}
									}
									break;
								}
								case EDIT_EMAIL:
									ccVal = GetWindowText( cbex->hEdit, szVal, MAXDATASIZE );
									break;
								default:
									ccVal = GetWindowText( cbex->hEdit, szVal, MAXDATASIZE );
									break;
							}
							
							SendMessage( hDlgDetails, PSM_GETCONTACT, NULL, ( LPARAM )&hContact );
							if( ( cbex->pItems[cbex->iSelectedItem].wFlags & CTRLF_CHANGED ) && !( hContact && ( cbex->pItems[cbex->iSelectedItem].wFlags & CTRLF_HASCUSTOM ) ) ) return 0;
							
							if( *szVal == 0 || !cbex->pItems[cbex->iSelectedItem].pszVal || _tcscmp( szVal, cbex->pItems[cbex->iSelectedItem].pszVal ) ) {
								cbex->pItems[cbex->iSelectedItem].wFlags |= CTRLF_CHANGED;
								cbex->pItems[cbex->iSelectedItem].wFlags |= ( hContact ? CTRLF_HASCUSTOM : CTRLF_HASPROTO );
								cbex->bIsChanged = TRUE;
								InvalidateRect( ( HWND )lParam, NULL, TRUE );
								SendMessage( hDlgDetails, PSM_CHANGED, NULL, NULL );
							}
							return 0;
						}
						case EN_KILLFOCUS:
						{
							INT ccText;
							
							if( !cbex->pItems || cbex->iSelectedItem < 0 || cbex->iSelectedItem >= cbex->numItems ) return 1;
							if( !( cbex->pItems[cbex->iSelectedItem].wFlags & CTRLF_CHANGED ) ) return 0;

							if( ( ccText = GetWindowTextLength( cbex->hEdit ) ) <= 0 ) {
								if( cbex->pItems[cbex->iSelectedItem].wFlags & CBEXIF_CATREADONLY ) {
									FREE( cbex->pItems[cbex->iSelectedItem].pszVal );
									SetWindowText( cbex->hEdit, _T( "" ) );
									cbex->pItems[cbex->iSelectedItem].wFlags &= ~CBEXIF_SMS;
								}
								else
									CbEx_WndProc( hwnd, CBEXM_DELITEM, NULL, cbex->iSelectedItem );
								SendMessage( GetParent( GetParent( hwnd ) ), PSM_CHANGED, NULL, NULL );
								cbex->bIsChanged = TRUE;
							}
							else
							if( cbex->pItems[cbex->iSelectedItem].pszVal = ( LPTSTR )realloc( cbex->pItems[cbex->iSelectedItem].pszVal, ( ccText + 2 ) * sizeof( TCHAR ) ) ) {
								cbex->pItems[cbex->iSelectedItem].pszVal[ccText + 1] = 0;
								GetWindowText( cbex->hEdit, cbex->pItems[cbex->iSelectedItem].pszVal, ccText + 1 );
							}
							return 0;
						}
					}
					break;
			}
			break;

		/**
		 * name:	CBEXM_ADDITEM
		 * desc:	add a item to the control
		 * param:	wParam - not used
		 *			lParam - ( LPCBEXITEM )&item
		 * return:	CB_ERR on failure, new item index if successful
		 **/
		case CBEXM_ADDITEM:
		{
			LPCBEXITEM	pItem = ( LPCBEXITEM )lParam;

			if( !pItem ) return FALSE;

			// if an item with the id of pItem exists, change it instead of adding a new one
			// but only if it has not been changed by the user yet.
			if( ( pItem->wMask & CBEXIM_ID ) && cbex->pItems && pItem->dwID != 0 ) {
				INT iIndex;
				
				for( iIndex = 0; iIndex < cbex->numItems; iIndex++ ) {
					if( cbex->pItems[iIndex].dwID == pItem->dwID ) {
						pItem->iItem = iIndex;
						if( cbex->pItems[iIndex].wFlags & CTRLF_CHANGED )
							pItem->wFlags |= CTRLF_CHANGED;
						else
							CbEx_WndProc( hwnd, CBEXM_SETITEM, 0, lParam );
						return iIndex;
					}
				}
			}

			// add a new item to the combobox
			if( !( cbex->pItems = ( LPCBEXITEMINTERN )realloc( cbex->pItems, ( cbex->numItems + 1 ) * sizeof( CBEXITEMINTERN ) ) ) ) {
				cbex->numItems = 0;
				return CB_ERR;
			}
		
			// set the ID
			cbex->pItems[cbex->numItems].dwID = ( pItem->wMask & CBEXIM_ID ) ? pItem->dwID : 0;

			// set category string
			if( !pItem->pszCat || !pItem->pszCat[0] || !mir_tcsncpy( cbex->pItems[cbex->numItems].szCat, pItem->pszCat, MAX_CAT ) ) {
				mir_snprintfT( cbex->pItems[cbex->numItems].szCat, MAX_CAT, _T( "%s %d" ), TranslateT( "Other" ), ++cbex->numOther );
			}

			// set value string
			if( ( pItem->wMask & CBEXIM_VAL ) && pItem->pszVal && pItem->pszVal[0] )
				cbex->pItems[cbex->numItems].pszVal = _tcsdup( pItem->pszVal );
			else
				cbex->pItems[cbex->numItems].pszVal = NULL;
			// set icon
			if( ( pItem->wMask & CBEXIM_ICONTEXT ) && pItem->pszIcon ) {
				cbex->pItems[cbex->numItems].pszIcon = pItem->pszIcon;
				cbex->pItems[cbex->numItems].hIcon = NIcoLib::GetIcon( pItem->pszIcon );
			}
			// set flags
			cbex->pItems[cbex->numItems].wFlags = ( pItem->wMask & CBEXIM_CAT ) ? pItem->wFlags : 0;

			cbex->numItems++;
			return cbex->numItems;
		}

		/**
		 * name:	CBEXM_SETITEM
		 * desc:	Set an item's information of the control.
		 *			If iItem member of CBEXITEM is -1, the currently selected item is changed.
		 * param:	wParam - not used
		 *			lParam - ( LPCBEXITEM )&item
		 * return:	CB_ERR on failure, new item index if successful
		 **/
		case CBEXM_SETITEM:
		{
			LPCBEXITEM	pItem = ( LPCBEXITEM )lParam;

			if( !PtrIsValid( pItem ) || !pItem->wMask || !PtrIsValid( cbex->pItems ) ) return FALSE;
			if( pItem->iItem == -1 ) pItem->iItem = cbex->iSelectedItem;
			if( pItem->iItem < 0 || pItem->iItem >= cbex->numItems ) return FALSE;

			// set new category string
			if( pItem->wMask & CBEXIM_CAT ) {
				// set category string
				if( !pItem->pszCat || !pItem->pszCat[0] || !mir_tcsncpy( cbex->pItems[pItem->iItem].szCat, pItem->pszCat, SIZEOF( cbex->pItems[pItem->iItem].szCat ) ) ) 
					mir_snprintfT( cbex->pItems[pItem->iItem].szCat, MAX_CAT, _T( "%s %d\0" ), TranslateT( "Other" ), ++cbex->numOther );
				if( pItem->iItem == cbex->iSelectedItem )
					SetWindowText( cbex->hBtnEdit, cbex->pItems[pItem->iItem].szCat );
			}
			// set new value
			if( pItem->wMask & CBEXIM_VAL ) {
				FREE( cbex->pItems[pItem->iItem].pszVal );
				if( pItem->pszVal && pItem->pszVal[0] )
					cbex->pItems[pItem->iItem].pszVal = _tcsdup( pItem->pszVal );
				if( pItem->iItem == cbex->iSelectedItem )
					SetWindowText( cbex->hEdit, cbex->pItems[pItem->iItem].pszVal ? cbex->pItems[pItem->iItem].pszVal : _T( "" ) );
			}

			// set icon
			if( ( pItem->wMask & CBEXIM_ICONTEXT ) && pItem->pszIcon ) {
				cbex->pItems[pItem->iItem].pszIcon = pItem->pszIcon;
				cbex->pItems[pItem->iItem].hIcon = NIcoLib::GetIcon( pItem->pszIcon );
				if( pItem->iItem == cbex->iSelectedItem )
					SendMessage( cbex->hBtnEdit, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )cbex->pItems[pItem->iItem].hIcon );
			}
			if( pItem->wMask & CBEXIM_FLAGS ) {
				cbex->pItems[pItem->iItem].wFlags = pItem->wFlags;
				CbEx_WndProc( hwnd, CBEXM_ENABLEITEM, NULL, NULL );
			}
			return TRUE;
		}

		/**
		 * name:	CBEXM_GETITEM
		 * desc:	Get an item from the control.
		 *			If iItem member of CBEXITEM is -1, the currently selected item is returned.
		 * param:	wParam - not used
		 *			lParam - ( LPCBEXITEM )&item
		 * return:	CB_ERR on failure, new item index if successful
		 **/
		case CBEXM_GETITEM:
		{
			LPCBEXITEM	pItem = ( LPCBEXITEM )lParam;

			if( !pItem || !cbex->pItems ) return FALSE;
			
			// try to find item by id
			if( ( pItem->wMask & CBEXIM_ID ) && pItem->dwID != 0 ) {
				INT i;

				for( i = 0; i < cbex->numItems; i++ ) {
					if( cbex->pItems[i].dwID == pItem->dwID )
						break;
				}
				pItem->iItem = i;
			}
			else
			if( pItem->iItem == -1 ) pItem->iItem = cbex->iSelectedItem;
			if( pItem->iItem < 0 || pItem->iItem >= cbex->numItems ) return FALSE;
			
			// return only currently selected itemindex
			if( !pItem->wMask ) return TRUE;
			// return the unique id
			if( pItem->wMask & CBEXIM_ID ) 
				pItem->dwID = cbex->pItems[pItem->iItem].dwID;
			// return category string
			if( ( pItem->wMask & CBEXIM_CAT ) && pItem->pszCat ) {
				if( *cbex->pItems[pItem->iItem].szCat != 0 )
					mir_tcsncpy( pItem->pszCat, cbex->pItems[pItem->iItem].szCat, pItem->ccCat - 1 );
				else
					*pItem->pszCat = 0;
			}
			// return value string
			if( ( pItem->wMask & CBEXIM_VAL ) && pItem->pszVal ) {
				if( cbex->pItems[pItem->iItem].pszVal )
					mir_tcsncpy( pItem->pszVal, cbex->pItems[pItem->iItem].pszVal, pItem->ccVal - 1 );
				else
					*pItem->pszVal = 0;
			}
			// return the icon
			if( pItem->wMask & CBEXIM_ICONTEXT )
				pItem->pszIcon = cbex->pItems[pItem->iItem].pszIcon;
			// return the flags
			if( pItem->wMask & CBEXIM_FLAGS )
				pItem->wFlags = cbex->pItems[pItem->iItem].wFlags;
			return TRUE;
		}

		/**
		 * name:	CBEXM_DELITEM
		 * desc:	delete an item from the control
		 * param:	wParam - not used
		 *			lParam - item index
		 * return:	CB_ERR on failure, new item index if successful
		 **/
		case CBEXM_DELITEM:
		{
			if( !cbex->pItems || ( INT )lParam < 0 || ( INT )lParam >= cbex->numItems || ( cbex->pItems[lParam].wFlags & CBEXIF_CATREADONLY ) )
				return FALSE;	
			FREE( cbex->pItems[( INT )lParam].pszVal );
			memmove( cbex->pItems + ( INT )lParam, 
				cbex->pItems + ( INT )lParam + 1,
				( cbex->numItems - ( INT )lParam - 1 ) * sizeof( CBEXITEMINTERN ) );
			cbex->numItems--;
			ZeroMemory( cbex->pItems + cbex->numItems, sizeof( CBEXITEMINTERN ) );
			CbEx_WndProc( hwnd, CBEXM_SETCURSEL, lParam - 1, FALSE );
			return TRUE;
		}

		/**
		 * name:	CBEXM_DELITEM
		 * desc:	delete an item from the control
		 * param:	wParam - not used
		 *			lParam - item index
		 * return:	CB_ERR on failure, new item index if successful
		 **/
		case CBEXM_DELALLITEMS:
		{
			INT i;

			if( PtrIsValid( cbex ) ) {
				if( PtrIsValid( cbex->pItems ) ) {
					for( i = 0; i < cbex->numItems; i++ ) {
						FREE( cbex->pItems[i].pszVal );
					}
					free( cbex->pItems );
					cbex->pItems = NULL;
				}
				cbex->numItems = 0;
				cbex->iSelectedItem = -1;
				SetWindowText( cbex->hEdit, _T( "" ) );
				SetWindowText( cbex->hBtnEdit, _T( "" ) );
				SendMessage( cbex->hBtnEdit, WM_SETICON, NULL, NULL );
			}
			return TRUE;
		}

		/**
		 * name:	CBEXM_ENABLEITEM
		 * desc:	enables or disables the current item
		 * param:	wParam - not used
		 *			lParam - not used
		 * return:	always 0
		 **/
		case CBEXM_ENABLEITEM:
			if( cbex->iSelectedItem >= 0 && cbex->iSelectedItem < cbex->numItems ) {
				HANDLE hContact;
				BOOLEAN bEnabled;
				
				PSGetContact( GetParent( hwnd ), hContact );

				bEnabled  = !hContact ||
							( cbex->pItems[cbex->iSelectedItem].wFlags & CTRLF_HASCUSTOM ) || 
							!( cbex->pItems[cbex->iSelectedItem].wFlags & ( CTRLF_HASPROTO|CTRLF_HASMETA ) ) ||
							!DB::Setting::GetByte( SET_PROPSHEET_PCBIREADONLY, 0 );

				EnableWindow( cbex->hBtnEdit, bEnabled );
				EnableWindow( cbex->hBtnDel, bEnabled && GetWindowTextLength( cbex->hEdit ) > 0 );
				EnableWindow( cbex->hEdit, bEnabled );
			}
			break;

		/**
		 * name:	CBEXM_ISCHANGED
		 * desc:	returns whether the control contains changed values or not
		 * param:	wParam - not used
		 *			lParam - not used
		 * return:	TRUE if control was changed, FALSE if nothing was edited
		 **/
		case CBEXM_ISCHANGED:
			return cbex->bIsChanged;

		/**
		 * name:	CBEXM_RESETCHANGED
		 * desc:	resets changed flag to FALSE
		 * param:	wParam - not used
		 *			lParam - not used
		 * return:	always FALSE
		 **/
		case CBEXM_RESETCHANGED:
			cbex->bIsChanged = 0;
			return 0;

		/**
		 * name:	CBEXM_SETCURSEL
		 * desc:	selects a certain item
		 * param:	wParam - index of the item to select
		 *			lParam - ( BOOLEAN )bValid - if TRUE, the next item with a value is selected
		 * return:	always FALSE
		 **/
		case CBEXM_SETCURSEL:
		{
			INT i;

			if( !cbex->pItems ) return 1;
			if( ( INT )wParam < 0 || ( INT )wParam >= cbex->numItems ) wParam = max( cbex->iSelectedItem, 0 );
			cbex->bLocked = 1;
			
			if( ( BOOLEAN )lParam == TRUE ) {
				INT i = ( INT )wParam;

				cbex->iSelectedItem = ( INT )wParam;
				while( i < cbex->numItems ) {
					if( cbex->pItems[i].pszVal && *cbex->pItems[i].pszVal ) {
						cbex->iSelectedItem = i;
						break;
					}
					i++;
				}
			}
			else {
				// search for the next none deleted item
				for( i = ( INT )wParam; i < cbex->numItems && *cbex->pItems[i].szCat == 0; i++ );
				if( i == cbex->numItems && ( INT )wParam > 0 ) {
					for( i = 0; i < ( INT )wParam && *cbex->pItems[i].szCat == 0; i++ );
					cbex->iSelectedItem = i == ( INT )wParam ? 0 : i;
				}
				else
					cbex->iSelectedItem = i;

			}
			SetWindowText( cbex->hBtnEdit, cbex->pItems[cbex->iSelectedItem].szCat );
			SetWindowText( cbex->hEdit, cbex->pItems[cbex->iSelectedItem].pszVal ? cbex->pItems[cbex->iSelectedItem].pszVal : _T( "" ) );
			SendMessage( cbex->hBtnEdit, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )cbex->pItems[cbex->iSelectedItem].hIcon );
			CbEx_WndProc( hwnd, CBEXM_ENABLEITEM, NULL, NULL );
			cbex->bLocked = 0;
			return 0;
		}
		case CBEXM_SORT:
			if( cbex->numItems > 4 ) {
				qsort( cbex->pItems + 3, cbex->numItems - 3, sizeof( CBEXITEMINTERN ), compareProc );
			}
			return 0;

		case WM_ERASEBKGND:
			return 1;

		case WM_SETFOCUS:
			SetFocus( cbex->hEdit );
			SendMessage( cbex->hEdit, EM_SETSEL, 0, ( LPARAM )-1 );
			return 0;
	}
	return DefWindowProc( hwnd, msg, wParam, lParam );
}

/**
 * name:	CbEx_UnLoadModule
 * desc:	calls required operations to clean up used memory and objects
 * param:	wParam - not used
 *			lParam - not used
 * return:	always 0
 **/
INT CbEx_UnLoadModule()
{
	UnregisterClass( UINFOCOMBOEXCLASS, ghInst );
	return 0;
}

/**
 * name:	CbEx_LoadModule
 * desc:	registers window class and does some other initializations
 * param:	none
 * return:	always 0
 **/
INT CbEx_LoadModule()
{
	WNDCLASSEX wc;
	
	ZeroMemory( &wc, sizeof( wc ) );
	wc.cbSize         = sizeof( wc );
	wc.lpszClassName  = UINFOCOMBOEXCLASS;
	wc.lpfnWndProc    = CbEx_WndProc;
	wc.hCursor        = LoadCursor( NULL, IDC_ARROW );
	wc.cbWndExtra     = sizeof( LPCBEX );
	wc.hbrBackground  = ( HBRUSH )GetStockObject( COLOR_WINDOW );
	wc.style          = CS_GLOBALCLASS;
	RegisterClassEx( &wc );
	return 0;
}

 
/**
 * name:	CbEx_AddItemFromDB
 * desc:	add a item read from db to the combobox
 * param:	hCtrl			- windowhandle to extended combobox control
 *			hIcon			- icon to use for custom items
 *			szItem			- category text for the item
 *			wForcedFlags	- force flag for each new entry
 *			hContact		- handle to contact whose settings to add
 *			szModule		- primary module to search the setting in
 *			szBaseProto		- contacts protocol module
 *			szSettingVal	- value holding setting
 * return:	TRUE	- if the item is not updated, because its changed flag is set
 *			FALSE	- if item is added or updated successfully
 **/
INT CbEx_AddItemFromDB(
				HWND hCtrl,
				LPCSTR szIcon,
				LPTSTR szItem,
				HANDLE hContact,
				LPCSTR szModule,
				LPCSTR szBaseProto,
				LPCSTR szSettingVal )
{
	DBVARIANT dbv;
	CBEXITEM cbi;
	LPTSTR sms;

	cbi.pszVal = NULL;
	cbi.dwID = hashSetting( szSettingVal );
	cbi.wFlags = CBEXIF_CATREADONLY|DB::Setting::GetTStringCtrl( hContact, szModule, szModule, szBaseProto, szSettingVal, &dbv );
	if( dbv.type >= DBVT_WCHAR ) {
		// no value read from database
		if( cbi.wFlags == CBEXIF_CATREADONLY ) {
			cbi.pszVal = NULL;
		}
		// check the database value
		else {
			cbi.pszVal = dbv.ptszVal;
			if( sms = _tcsstr( cbi.pszVal, _T( " SMS\0" ) ) ) {
				cbi.wFlags |= CBEXIF_SMS;
				*sms = 0;
			}
		}
	}
	cbi.pszCat = szItem;
	cbi.iItem = -1;
	cbi.wMask = CBEXIM_ALL;
	cbi.pszIcon = szIcon;
	SendMessage( hCtrl, CBEXM_ADDITEM, NULL, ( LPARAM )&cbi );
	DB::Variant::Free( &dbv );
	return ( cbi.wFlags & CTRLF_CHANGED ) == CTRLF_CHANGED;
}

/**
 * name:	CbEx_AddMyItemsFromDB
 * desc:	add a item read from db to the combobox
 * param:	hCtrl			- windowhandle to extended combobox control
 *			hIcon			- icon to use for custom items
 *			wForcedFlags	- force flag for each new entry
 *			hContact		- handle to contact whose settings to add
 *			szModule		- primary module to search the setting in
 *			szBaseProto		- contacts protocol module
 *			szFormatCat		- format for the category holding setting
 *			szFormatVal		- format for the value holding setting
 * return:	TRUE	- if one of the items was not updated, because its changed flag is set
 *			FALSE	- if all items were added or updated successfully
 **/
INT CbEx_AddMyItemsFromDB(
				HWND hCtrl,
				LPCSTR szIcon,
				WORD wForcedFlags,
				HANDLE hContact,
				LPCSTR szModule,
				LPCSTR szBaseProto,
				LPCSTR szFormatCat,
				LPCSTR szFormatVal )
{
	CBEXITEM cbi;
	DBVARIANT dbv;
	CHAR szSetting[MAXSETTING];
	WORD i;
	LPTSTR sms;
	INT bAnyItemIsChanged = 0;

	ZeroMemory( &cbi, sizeof( cbi ) );
	cbi.iItem = -1;
	cbi.wMask = CBEXIM_ALL;
	cbi.pszIcon = szIcon;

	for( i = 0;
		SUCCEEDED( mir_snprintfA( szSetting, MAXSETTING, szFormatVal, i ) ) &&
		( cbi.wFlags = DB::Setting::GetTStringCtrl( hContact, szModule, szModule, szBaseProto, szSetting, &dbv ) );
		i++ )
	{
		// read value
		cbi.dwID = hashSetting( szSetting );
		cbi.pszVal = dbv.ptszVal;
		dbv.type = DBVT_DELETED;
		dbv.ptszVal = NULL;

		// read category
		if( SUCCEEDED( mir_snprintfA( szSetting, MAXSETTING, szFormatCat, i ) ) ) {
			if( cbi.wFlags & CTRLF_HASCUSTOM ) {
				if( DB::Setting::GetTString( hContact, szModule, szSetting, &dbv ) )
					dbv.type = DBVT_DELETED;
			}
			else
			if( cbi.wFlags & CTRLF_HASPROTO ) {
				if( DB::Setting::GetTString( hContact, szBaseProto, szSetting, &dbv ) )
					dbv.type = DBVT_DELETED;
			}

			if( dbv.type > DBVT_DELETED && dbv.ptszVal && *dbv.ptszVal ) {
				cbi.pszCat = dbv.ptszVal;
				dbv.type = DBVT_DELETED;
				dbv.ptszVal = NULL;
			}
		}
		if( sms = _tcsstr( cbi.pszVal, _T( " SMS" ) ) ) {
			cbi.wFlags |= CBEXIF_SMS;
			*sms = 0;
		}
		cbi.wFlags |= wForcedFlags;
		if( CB_ERR == SendMessage( hCtrl, CBEXM_ADDITEM, NULL, ( LPARAM )&cbi ) ) 
			break;
		bAnyItemIsChanged |= ( cbi.wFlags & CTRLF_CHANGED ) == CTRLF_CHANGED;
		if( cbi.pszCat ) { 
			mir_free( cbi.pszCat );
			cbi.pszCat = NULL; 
		}
		if( cbi.pszVal ) {
			mir_free( cbi.pszVal );
			cbi.pszVal = NULL;
		}
	}
	SendMessage( hCtrl, CBEXM_SORT, NULL, NULL );
	return bAnyItemIsChanged;
}

/**
 * name:	CbEx_WriteItemToDB
 * desc:	write a item from combobox to database
 * param:	none
 * return:	always 0
 **/
INT CbEx_WriteItemToDB(
				HWND hCtrl,
				HANDLE hContact,
				LPCSTR szModule,
				LPCSTR szBaseProto,
				LPCSTR szSetting )
{
	TCHAR szVal[MAXDATASIZE];
	CBEXITEM cbi;

	if( !CbEx_WndProc( hCtrl, CBEXM_ISCHANGED, NULL, NULL ) ) return 1;

	cbi.wMask = CBEXIM_ID|CBEXIM_VAL|CBEXIM_FLAGS;
	cbi.pszVal = szVal;
	cbi.ccVal = MAXDATASIZE - 4;
	cbi.iItem = 0;
	cbi.dwID = hashSetting( szSetting );
	if( !CbEx_WndProc( hCtrl, CBEXM_GETITEM, NULL, ( LPARAM )&cbi ) ) return 1;
	if( !( cbi.wFlags & CTRLF_CHANGED ) ) return 0;
	if( !hContact && !( szModule = szBaseProto ) ) return 1;
	if( !*szVal )
		DB::Setting::Delete( hContact, szModule, szSetting );
	else {
		if( cbi.wFlags & CBEXIF_SMS ) {
			mir_tcsncat( szVal, _T( " SMS" ), SIZEOF( szVal ) );
		}
		if( DB::Setting::WriteTString( hContact, szModule, szSetting, szVal ) ) return 1;
	}
	cbi.wFlags &= ~CTRLF_CHANGED;
	cbi.wMask = CBEXIM_FLAGS;
	CbEx_WndProc( hCtrl, CBEXM_SETITEM, NULL, ( LPARAM )&cbi );
	InvalidateRect( GetDlgItem( hCtrl, EDIT_VALUE ), NULL, TRUE );
	return 0;
}

/**
 * name:	CbEx_WriteMyItemsToDB
 * desc:	write a list of custom items from combobox to database
 * param:	none
 * return:	always 0
 **/
INT CbEx_WriteMyItemsToDB(
				HWND hCtrl,
				INT iFirstItem,
				HANDLE hContact,
				LPCSTR szModule,
				LPCSTR szBaseProto,
				LPCSTR szFormatCat,
				LPCSTR szFormatVal )
{
	CHAR szSetting[MAXSETTING];
	TCHAR szCat[MAX_CAT];
	TCHAR szVal[MAXDATASIZE];
	LPTSTR pszOther;
	CBEXITEM cbi;
	WORD ccOther;
	INT i = 0;

	if( !CbEx_WndProc( hCtrl, CBEXM_ISCHANGED, NULL, NULL ) ) return 1;
	if( !hContact && !( szModule = szBaseProto ) ) return 1;
	
	pszOther = TranslateT( "Other" );
	ccOther = _tcslen( pszOther );
	cbi.wMask = CBEXIM_CAT|CBEXIM_VAL|CBEXIM_FLAGS;
	cbi.pszCat = szCat;
	cbi.ccCat = MAX_CAT;
	cbi.pszVal = szVal;
	cbi.ccVal = MAXDATASIZE - 4;
	cbi.iItem = iFirstItem;
	cbi.dwID = 0;
	
	while( CbEx_WndProc( hCtrl, CBEXM_GETITEM, NULL, ( LPARAM )&cbi ) && cbi.iItem < 50 ) {
		if( !( cbi.wFlags & CBEXIF_DELETED ) && *szVal ) {
			if( cbi.wFlags & CBEXIF_SMS ) {
				mir_tcsncat( szVal, _T( " SMS" ), SIZEOF( szVal ) );
			}
			mir_snprintfA( szSetting, MAXSETTING, szFormatCat, i );
			if( *szCat && _tcsncmp( szCat, pszOther, ccOther ) ) {
				if( DB::Setting::WriteTString( hContact, szModule, szSetting, szCat ) ) return 1;
			}
			else
				DB::Setting::Delete( hContact, szModule, szSetting );
			mir_snprintfA( szSetting, MAXSETTING, szFormatVal, i );
			if( DB::Setting::WriteTString( hContact, szModule, szSetting, szVal ) ) return 1;
			cbi.wFlags &= ~CTRLF_CHANGED;
			cbi.wMask = CBEXIM_FLAGS;
			CbEx_WndProc( hCtrl, CBEXM_SETITEM, NULL, ( LPARAM )&cbi );
			cbi.wMask = CBEXIM_CAT|CBEXIM_VAL|CBEXIM_FLAGS;
			i++;
		}
		
		cbi.iItem++;
	}
	DB::Setting::DeleteArray( hContact, szModule, szFormatCat, i );
	DB::Setting::DeleteArray( hContact, szModule, szFormatVal, i );
	InvalidateRect( GetDlgItem( hCtrl, EDIT_VALUE ), NULL, TRUE );
	return 0;
}